#include "netlib/base/thread.h"

#include <sys/prctl.h>
#include <syscall.h>
#include <unistd.h>

#include <cassert>
#include <cstdio>
#include <utility>

#include "netlib/base/current_thread.h"
#include "netlib/base/exception.h"
#include "netlib/base/logger.h"

namespace netlib {
namespace detail {

int Gettid() { return static_cast<int>(::syscall(SYS_gettid)); }

struct ThreadData {
	Thread::ThreadFunc func_;
	std::string name_;
	pid_t* tid_;
	CountDownLatch* latch_;

	ThreadData(Thread::ThreadFunc func, std::string name, pid_t* tid, CountDownLatch* latch) :
	    func_(std::move(func)), name_(std::move(name)), tid_(tid), latch_(latch) {}

	void RunInThread() {
		*tid_ = netlib::current_thread::Tid();
		tid_ = nullptr;
		latch_->CountDown();
		latch_ = nullptr;

		netlib::current_thread::t_name = name_.empty() ? "muduoThread" : name_.c_str();
		::prctl(PR_SET_NAME, netlib::current_thread::t_name);
		try {
			func_();
			netlib::current_thread::t_name = "finished";
		} catch (const Exception& ex) {
			netlib::current_thread::t_name = "crashed";
			fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
			fprintf(stderr, "reason: %s\n", ex.what());
			fprintf(stderr, "stack trace: %s\n", ex.StackTrace());
			abort();
		} catch (const std::exception& ex) {
			netlib::current_thread::t_name = "crashed";
			fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
			fprintf(stderr, "reason: %s\n", ex.what());
			abort();
		} catch (...) {
			netlib::current_thread::t_name = "crashed";
			fprintf(stderr, "unknown exception caught in Thread %s\n", name_.c_str());
			throw; // rethrow
		}
	}
};

void* StartThread(void* obj) {
	auto* data = static_cast<ThreadData*>(obj);
	data->RunInThread();
	delete data;
	return nullptr;
}

} // namespace detail

void current_thread::CacheTid() {
	if (0 == t_tid_cache) {
		t_tid_cache = detail::Gettid();
		t_tid_str_len = snprintf(t_tid_str, sizeof(t_tid_str), "%5d", t_tid_cache);
	}
}

} // namespace netlib

netlib::AtomicInt32 netlib::Thread::num_created;

netlib::Thread::Thread(ThreadFunc func, const std::string& name) :
    func_(std::move(func)), name_(name) {
	int num = num_created.IncrementAndGet();
	if (name.empty()) {
		char buf[32];
		snprintf(buf, sizeof buf, "Thread%d", num);
		name_ = buf;
	}
}

netlib::Thread::~Thread() {
	if (is_started_ && !is_joined_) {
		pthread_detach(pthread_);
	}
}

void netlib::Thread::Start() {
	assert(!is_started_ && !is_joined_);
	is_started_ = true;
	auto* thread_data = new detail::ThreadData(func_, name_, &tid_, &latch_);
	if (pthread_create(&pthread_, nullptr, &detail::StartThread, thread_data)) {
		is_started_ = false;
		delete thread_data;
		LOG_SYSFATAL << "Failed in pthread_create";
	} else {
		latch_.Wait();
		assert(tid_ > 0);
	}
}

int netlib::Thread::Join() {
	assert(is_started_);
	assert(!is_joined_);
	is_joined_ = true;
	return pthread_join(pthread_, nullptr);
}
